package camid.distribution.broker;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import camid.distribution.interceptor.InterceptorChain;
import camid.distribution.interceptor.InvocationEvaluator;
import camid.distribution.interceptor.InvocationInterceptor;
import camid.distribution.protocol.Endpoint;
import camid.distribution.protocol.InvocationDescriptor;
import camid.distribution.protocol.MethodRequestMessage;
import camid.distribution.protocol.MethodResponseMessage;
import camid.distribution.protocol.ResponseStatus;
import camid.infrastructure.Connector;
import camid.infrastructure.SenderQueue;
import camid.services.nodemanager.ForwardingException;
import camid.util.monitoring.JMeterClientLogger;


public class Requestor {

	
	private SenderQueue queue;
	private List<InvocationInterceptor> interceptors;
	
	private JMeterClientLogger jmLogger;
	
	private static final Logger logger = LogManager.getLogger(Requestor.class.getName());
	
	public Requestor(SenderQueue senderQueue) 
	{
		this.queue = senderQueue;
		this.interceptors = Collections.synchronizedList( 
				new ArrayList<InvocationInterceptor> ());
		this.jmLogger = JMeterClientLogger.getInstance();
	}

	public Object request(Map<String,Object> invocationContext, 
			Endpoint endpoint, 
			InvocationDescriptor descriptor) throws Exception 
	{
		MethodRequestMessage requestMsg = makeRequestMessage(invocationContext, endpoint, descriptor);
		MethodResponseMessage responseMsg = new MethodResponseMessage();
		
		
		Serializable response = null;
		boolean forwarded = false;
		Long startTime = 0L;
//		try
//		{
//			for(InvocationInterceptor interceptor : interceptors)
//			{
//				interceptor.beforeInvocation(requestMsg);
//			}
//			
//			Connector sender = new Connector(requestMsg.getEndpoint());
//			
//			startTime = System.currentTimeMillis();
//			responseMsg = queue.doSend(sender, requestMsg);
//			
//			
//		}
//		catch (Exception e) 
//		{
//			responseMsg.setResponse(e);
//			responseMsg.setStatus(ResponseStatus.SERVICE_EXCEPTION);
//		}
//		
//		try
//		{
//			synchronized(interceptors)
//			{
//				for(InvocationInterceptor interceptor : interceptors)
//				{
//					interceptor.afterInvocation(requestMsg, responseMsg);
//				}
//			}
//		}
//		catch(Exception e)
//		{
//			throw e;
//		}
		
		RequestorEvaluator evaluator = new RequestorEvaluator(this.queue);
		InterceptorChain interceptorChain = new InterceptorChain(requestMsg, interceptors, evaluator);
		responseMsg = interceptorChain.proceed();
		
		
		if(responseMsg != null)
		{
			response = responseMsg.getResponse();
			if(response != null && response instanceof ForwardingException)
			{
				forwarded = true;
			}
		}
		
		if(forwarded)
		{
			logger.info("Downtime (" + descriptor.getEntityClass() + "/" + descriptor.getObjectId() + "/"
					+ descriptor.getMethod() + ") => " + (System.currentTimeMillis() - startTime));
			jmLogger.info("Downtime (" + descriptor.getEntityClass() + "/" + descriptor.getObjectId() + "/"
					+ descriptor.getMethod() + ") => " + (System.currentTimeMillis() - startTime));
		}
		
//		adjustEndpoint(endpoint,responseMsg);
		
		
		response = responseMsg.getResponse();
		if(response instanceof Exception)
		{
			throw ((Exception)response);
		}
		
		
		return response;
	}

	private void adjustEndpoint(Endpoint endpoint,
			MethodResponseMessage responseMsg) {
	
		if( responseMsg != null)
		{
			Endpoint responseEp = responseMsg.getEndpoint();
			if(endpoint != null && responseEp != null)
			{
				endpoint.setHostname(responseEp.getHostname());
				endpoint.setPort(responseEp.getPort());
			}
		}
	}

	private MethodRequestMessage makeRequestMessage(Map<String, Object> invocationContext, Endpoint endpoint,
			InvocationDescriptor descriptor) {
		
		MethodRequestMessage requestMsg = new MethodRequestMessage();
		requestMsg.setInvocationContext(invocationContext);
		requestMsg.setDescriptor(descriptor);
		requestMsg.setVmId("0");
		requestMsg.setEndpoint(endpoint);
		return requestMsg;
	}
	
	public void addInterceptor(InvocationInterceptor interceptor) 
	{
		this.interceptors.add(interceptor);	
	}

	public void shutdown() 
	{
		if(queue != null)
			this.queue.shutdown();	
	}
	
	private static class RequestorEvaluator implements InvocationEvaluator {

		private SenderQueue queue;
		
		public RequestorEvaluator(SenderQueue queue) {
			this.queue = queue;
		}
		
		@Override
		public MethodResponseMessage evaluate(
				MethodRequestMessage requestMsg) {
			
			MethodResponseMessage responseMsg = new MethodResponseMessage();
			
			try
			{				
				Connector sender = new Connector(requestMsg.getEndpoint());
				responseMsg = queue.doSend(sender, requestMsg);
			}
			catch (Exception e) 
			{
//				throw new RuntimeException(e);
				responseMsg.setResponse(e);
				responseMsg.setStatus(ResponseStatus.SERVICE_EXCEPTION);
			}
			Map<String, Object> requestContext = requestMsg.getInvocationContext();
			
			Map<String, Object> context = responseMsg.getInvocationContext();
			for(Entry<String, Object> contextEntry : context.entrySet()) {
				String nodeId = contextEntry.getKey();
				Object value = contextEntry.getValue();
				requestContext.put(nodeId, value);
			}
			
			return responseMsg;
		}
		
		
		
	}

}
